/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions;

import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.BuildLog;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import com.softwarecraftsmen.orogeny.filing.*;
import org.jetbrains.annotations.NotNull;

import static com.softwarecraftsmen.archivers.zip.ZipArchiver.ZipArchive;
import static com.softwarecraftsmen.orogeny.actions.ArchiveAction.archiveAs;
import static com.softwarecraftsmen.orogeny.actions.ClassExistsAction.classExists;
import static com.softwarecraftsmen.orogeny.actions.CopyFilesAction.flatHardLinkFiles;
import static com.softwarecraftsmen.orogeny.actions.CopyFilesAction.hardLinkFiles;
import static com.softwarecraftsmen.orogeny.actions.MakeDirectoryAction.makeDirectory;
import static com.softwarecraftsmen.orogeny.actions.ManifestAction.manifest;
import static com.softwarecraftsmen.orogeny.actions.RegisterAction.register;
import static com.softwarecraftsmen.orogeny.actions.TouchAction.touch;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectories.absoluteDirectories;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import static com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter.*;

public class ModuleAction extends AbstractAction
{
	private final Action[] actions;

	public ModuleAction(final @NotNull Action... actions)
	{
		this.actions = actions;
	}

	@NotNull
	public String describeWhatWeDo()
	{
		return "module";
	}

	@NotNull
	public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		for (Action action : actions)
		{
			final SuccessOrFailure successOrFailure = action.execute(buildLog, buildEnvironment);
			if (successOrFailure.isFailure())
			{
				return successOrFailure;
			}
		}
		return Success;
	}

	@NotNull
	public static SpecifyName module(final @NotNull String name)
	{
		if (name.isEmpty())
		{
			throw new IllegalArgumentException("Module names can not be empty");
		}
		return new SpecifyName(name);
	}

	public static final class SpecifyName
	{
		private static final AbsolutePaths[] EmptyClassPath = new AbsolutePaths[]{};
		private final String name;

		public SpecifyName(final @NotNull String name)
		{
			this.name = name;
		}

		@NotNull
		public SpecifySource compile(final @NotNull AbsoluteDirectory... sourceDirectories)
		{
			return new SpecifySource(absoluteDirectories(sourceDirectories));
		}

		@NotNull
		public SpecifySource compile(final @NotNull AbsoluteDirectories sourceDirectories)
		{
			return new SpecifySource(sourceDirectories);
		}

		public final class SpecifySource
		{
			private final AbsoluteDirectories sourceDirectories;

			private SpecifySource(final @NotNull AbsoluteDirectories sourceDirectories)
			{
				this.sourceDirectories = sourceDirectories;
			}

			@NotNull
			public SpecifyClassPath usingClassPath()
			{
				return usingClassPath(EmptyClassPath);
			}

			@NotNull
			public SpecifyClassPath usingClassPath(final @NotNull AbsolutePath... classPath)
			{
				return usingClassPath(absoluteDirectoriesAndFiles(classPath));
			}

			@NotNull
			public SpecifyClassPath usingClassPath(final @NotNull AbsolutePaths... classPath)
			{
				return new SpecifyClassPath(absoluteDirectoriesAndFiles(classPath));
			}

			public final class SpecifyClassPath
			{
				private final AbsolutePaths classPath;

				private SpecifyClassPath(final @NotNull AbsolutePaths classPath)
				{
					this.classPath = classPath;
				}
				@NotNull
				public SpecifyOutputDirectory toOutputDirectory(final @NotNull AbsoluteDirectory outputDirectory)
				{
					return new SpecifyOutputDirectory(outputDirectory);
				}

				public final class SpecifyOutputDirectory
				{
					private final AbsoluteDirectory outputDirectory;

					private SpecifyOutputDirectory(final @NotNull AbsoluteDirectory outputDirectory) {this.outputDirectory = outputDirectory;}

					@NotNull
					public ModuleAction withoutMainClass()
					{
						return withMainClass("");
					}

					@NotNull
					public ModuleAction withMainClass(final @NotNull String mainClassName)
					{
						final AbsoluteDirectory moduleOutputDirectory = outputDirectory.subDirectory(name);
						final AbsoluteDirectory generatedSourceDirectory = moduleOutputDirectory.subDirectory("generatedSource");
						final AbsoluteDirectory distributionDirectory = moduleOutputDirectory.subDirectory("distribution");
						final AbsoluteDirectory classesDirectory = moduleOutputDirectory.subDirectory("classes");
						final String jarName = name + ".jar";
						final AbsoluteFile jarFile = distributionDirectory.file(jarName);
						final AbsoluteFile sourceZipFile = moduleOutputDirectory.file(name + ".source.zip");
						final AbsolutePaths registrationPath = classPath.and(jarFile);
						return new ModuleAction
						(
							makeDirectory(generatedSourceDirectory),
							CompileAction.compile(sourceDirectories).usingClassPath(classPath).toOutputDirectory(classesDirectory).processAnnotationsWith().generatingSourceTo(generatedSourceDirectory),
							hardLinkFiles(AllFilesExcludingHiddenFilesAndHiddenFoldersAndJavaSource.exclude(HasIntelliJFileExtension)).from(sourceDirectories.and(generatedSourceDirectory)).to(classesDirectory),
							classExists(mainClassName).in(classesDirectory).unless(mainClassName.isEmpty()),
							manifest(classesDirectory).withClassPath(classPath).withMainClass(mainClassName),
							touch(classesDirectory).where(AllFilesExcludingHiddenFilesAndHiddenFolders).withBuildDateTime(),
							makeDirectory(distributionDirectory),
							archiveAs(ZipArchive).to(jarFile).roots(classesDirectory).capturing(AllFilesExcludingHiddenFilesAndHiddenFolders),
							touch(jarFile).where(HasJarFileExtension).withBuildDateTime(),
							archiveAs(ZipArchive).to(sourceZipFile).roots(sourceDirectories.and(generatedSourceDirectory)).capturing(AllFilesExcludingHiddenFilesAndHiddenFolders),
							touch(sourceZipFile).where(HasZipFileExtension).withBuildDateTime(),
							flatHardLinkFiles(ZipOrJar).from(classPath).to(distributionDirectory),
							register(name).asPaths(registrationPath)
						);
					}
				}
			}
		}
	}
}
